use crate::data::cors::CorsOptions;
use crate::data::message::Message;
use crate::fairing::user::UserDb;
use crate::model::user::{UserData, UserId, UserLoginCredential, Username};
use crate::token::{sign_token, TokenInformation, TOKEN_VALID_TIME};
use rocket::http::Status;
use rocket::request::Request;
use rocket::response::{self, Responder, Response};
use rocket::route::Route;
use rocket::serde::{json::Json, Serialize};
use rocket_db_pools::{
    sqlx::{self, Acquire},
    Connection,
};
use std::time::{SystemTime, UNIX_EPOCH};

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct LoginResult {
    token: String,
    user_id: UserId,
    username: Username,
    is_admin: bool,
    expire_time: u64,
}

enum LoginStatus {
    Ok(Json<LoginResult>),
    Unmatched,
    DatabaseErr(sqlx::Error),
    SignErr(Box<dyn std::error::Error>),
}

impl<'r> Responder<'r, 'static> for LoginStatus {
    fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
        match self {
            LoginStatus::Ok(result) => Response::build_from(result.respond_to(req)?).ok(),
            LoginStatus::Unmatched => Response::build_from(
                Json(Message {
                    message: "登录失败。请检查你的信息并重试。".to_string(),
                })
                .respond_to(req)?,
            )
            .status(Status::Unauthorized)
            .ok(),
            LoginStatus::DatabaseErr(err) => Response::build_from(
                Json(Message {
                    message: err.to_string(),
                })
                .respond_to(req)?,
            )
            .status(Status::InternalServerError)
            .ok(),
            LoginStatus::SignErr(err) => Response::build_from(
                Json(Message {
                    message: err.to_string(),
                })
                .respond_to(req)?,
            )
            .status(Status::InternalServerError)
            .ok(),
        }
    }
}

static LOGIN_OPTIONS: CorsOptions = CorsOptions {
    allowed_methods: &["POST"],
    allowed_headers: &[],
};

#[options("/login")]
async fn login_cors() -> &'static CorsOptions {
    &LOGIN_OPTIONS
}

struct SignResult {
    token: String,
    expire_time: u64,
}

async fn sign_token_for_user(user: &UserData) -> Result<SignResult, Box<dyn std::error::Error>> {
    let current_time = SystemTime::now();
    let current_time = current_time.duration_since(UNIX_EPOCH).unwrap();
    let issued_time = current_time.as_secs();
    let info = TokenInformation {
        user_id: user.identifier.id,
        issued_time,
    };
    let expire_time = (current_time + TOKEN_VALID_TIME).as_secs();
    let token = sign_token(&info).await?;
    Ok(SignResult { token, expire_time })
}

#[post("/login", data = "<credentials>")]
async fn login(mut db: Connection<UserDb>, credentials: Json<UserLoginCredential>) -> LoginStatus {
    let result = async {
        let mut tx = db.begin().await?;
        let user = credentials.0.login(&mut tx).await?;
        tx.commit().await?;
        Ok(user)
    }
    .await;
    match result {
        Ok(user_option) => match user_option {
            Some(user) => {
                let result = sign_token_for_user(&user).await;
                match result {
                    Ok(result) => LoginStatus::Ok(Json(LoginResult {
                        username: user.identifier.username,
                        user_id: user.identifier.id,
                        is_admin: user.info.is_admin,
                        token: result.token,
                        expire_time: result.expire_time,
                    })),
                    Err(err) => LoginStatus::SignErr(err),
                }
            }
            None => LoginStatus::Unmatched,
        },
        Err(err) => LoginStatus::DatabaseErr(err),
    }
}

pub fn route() -> Vec<Route> {
    routes![login, login_cors]
}
